在開發階段通常會建立許許多多的函式來開發功能,為了有效管理程式碼,有時會重複地使用部份函式,所以我們今天要來實做在SPA中,如何管理並使用共用的函式,以下正式開始進入主題。
這裡拿前幾天實做彈出視窗的例子,載入Home頁面時會顯示公告視窗,並且搭配state來控制只讓視窗在第一次出現。這邊想要在介面上做一個按鈕,當按下時視窗會再次跳出。我們試試在render裡加入button:
src/pages/Home.js
export const Home = {
state: {
//預設顯示狀態
show: true,
},
mount: function () {
if (this.state.show === true) {
//呼叫modal
$('#modal').modal('show')
}
//寫入狀態
this.state.show = false
},
render: () => {
const modal = `
<div class="modal" tabindex="-1" id="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">重要公告</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>這裡是重要公告的內容....blablabla</p>
</div>
</div>
</div>
</div>
`
const content = `
<div class="container">
<h1>Home page</h1>
<div>Welcome to my page!</div>
<button class="btn btn-primary" id="button">顯示公告</button>
</div>
${modal}
`
return App.render(content)
},
}
我們加了一個「顯示公告」的按鈕,並給予id值button,作為綁定監聽事件用。接著在Home裡新增listener屬性,註冊事件類型與handler:
src/pages/Home.js
export const Home = {
listener: {
click: function (e) {
if (e.target.id === 'button') {
//呼叫modal
$('#modal').modal('show')
}
},
},
state: {
//...
這裡可以看到事件類型是click,由於我們對body進行綁定,所以運用監聽事件的冒泡階段來獲得點擊時的目標,判斷事件目標的id,如果符合才會執行顯示視窗。
重點來了,看到這裡應該就會發現,在listener與mount有重複的彈出視窗程式碼,雖然這邊僅有短短一行,但這畢竟是用jQuery做的示範,真實情況可能會有很多行。如果在元件內有類似這樣的共同函式,該怎麼應用呢?
src/pages/Home.js
const showModal = () => {
$('#modal').modal('show')
}
export const Home = {
//...
第一種是直接在外部直接宣告共用到的函式(showModal),當要調用時直接呼叫showModal()即可。好處是簡單方便,適合單一行為且複雜性低的動作。
src/pages/Home.js
export const Home = {
func: {
showModal: () => {
$('#modal').modal('show')
},
},
listener: {
//...
第二種相信你們應該也猜到了,在元件新增一個func屬性,裡面在定義屬性與callback,要調用時可以用Home.func.showModal()做呼叫,好處是結構化呈現,在調用上可以明確知道是來自Home元件的函式。
好了,看到這裡其實兩個是差不多的。這兩種方式哪個比較優?沒有明確的標準答案,要視開發複雜程度與習慣而定,若單純在Component內部使用,其實沒有太大的差別,都可以相互調用和帶入參數;但如果要輸出模組給別的Component使用,就會比較推第二種方法(關於這點後面文章會來實做)。
我們常會看到許多網站在讀取時,會顯示一個圈圈轉呀轉,等到讀取完畢時才會消失,這個東西叫spinner或loader。這個功能幾乎所有頁面都會使用到,但不太可能在SPA裡每頁宣告一樣的spinner函式,所以會需要設定在全域供任何地方調用。這裡該如何應用呢?
這個方法跟昨天實做的eventListener.js很相像,首先在src/utils目錄下新增一個func.js,作為共同使用函式的模組:
src/utils/func.js
export const loading = () => {
const spinners = `
<div class="d-flex justify-content-center align-items-center bg-light h-100 w-100 fixed-top" id="spinner" style="z-index:1100">
<div class="spinner-grow text-primary" role="status"></div>
</div>
`
//插入HTML至指定元件最後一個子元素之後
document.querySelector('body').insertAdjacentHTML('beforeend', spinners)
//模擬讀取2秒後移除spinners
setTimeout(function () {
document.querySelector('#spinner').remove()
}, 2000)
}
內容的部份使用到Bootstrap的spinners,因為會讓所有頁面共用所以需要做命名輸出模組。我們在spinner外面套一層div,運用css flex並讓它擴展至全畫面,接著渲染在畫面上使用了Element.insertAdjacentHTML方法,表示會將spinner插入body節點最後一個子元素之後(也就是body標籤結束前面)。最後使用setTimeout這個定時器方法,2秒時間一到就將spinner做移除,來模擬開始到讀取完畢這段存取後端的時間差。
最後記得要在使用的地方做匯入,並呼叫匯入的函式:
import { loading } from '../utils/func'
export const Home = {
//...
mount: function () {
//顯示讀取
loading()
//...
另外這裡有稍微做一點手腳,為了使的讀取畫面會保持在最上層,在spinner的外層容器給予style並設置z-index值高過彈窗modal。可以看到Bootstrap裡modal的z-index設定值為1050。
這個方式其實跟剛剛模式、內容完全一樣,只是建立的位置不一樣。我們可以在src下建立components,這目錄專門用來放共同使用的元件(注意與pages裡的Component做分隔),例如sidebar可以是一個component、spinner也是一個模組...等(使用時記得一樣要匯入函式後呼叫)。
如果你問哪種方法好,還是同一句話,要視開發複雜程度與習慣。如果共用函式少,其實不太需要匯出太多模組到components資料夾,在func.js集中做管理就好,因為也就那幾個共用函式。如果共用函式數量可觀,那麼建議可另外輸出專門的模組,名字也比較好認。
不管用什麼方式實做,相信在評估後可以找到適合自己開發的方式,只要可以把共同的函式給管理好,透過模組化輸出匯入開發並且能夠正常使用,選什麼方法也就不是問題了。今天內容就到這裡,我們明天見!